/*
 * @Author: soso
 * @Date: 2022-01-13 11:10:54
 * @LastEditTime: 2022-03-09 17:00:23
 * @LastEditors: Please set LastEditors
 * @Description: 文件写入操作-带阻塞锁
 * @FilePath: /file-sync/utils/file.go
 */

package utils

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"errors"
	"io"
	"io/fs"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

/**
 * @description: 创建一个空文件
 * @param {string} filePath
 * @return {*}
 */
func TouchAFile(filePath string) error {
	dir := filepath.Dir(filePath)
	if dir != "." {
		os.MkdirAll(dir, os.ModePerm)
	}
	file, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer file.Close()
	return err
}

/**
 * @description: 使用系统锁，写入文件
 * @param {string} filename
 * @param {[]byte} data
 * @param {fs.FileMode} perm
 * @return {*}
 */
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
	dir := filepath.Dir(filename)

	if _, err := os.Stat(dir); err != nil {
		os.MkdirAll(dir, os.ModePerm)
	}

	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
	if err != nil {
		return err
	}

	flock, err := FlockCreate(f)
	if err != nil {
		return err
	}
	defer flock.Release()

	err = flock.Lock(false)
	if err != nil {
		return err
	}
	defer flock.Unlock()

	n, err := f.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}

	if err1 := f.Close(); err == nil {
		err = err1
	}
	return err
}

/**
 * 创建一个固定大小的0填充文件
 */
func CreateEmptyFile(filePath string, size int64) error {
	_, err := os.Stat(filePath)
	if err == nil {
		os.Remove(filePath)
	} else {
		os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
	}

	fd, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer fd.Close()
	_, err = fd.Seek(size-1, 0)
	if err != nil {
		return err
	}
	_, err = fd.Write([]byte{0})
	if err != nil {
		return err
	}
	return nil
}

/**
 * @description: 追加方式添加内容
 * @param {string} filename
 * @param {[]byte} buf
 * @return {*}
 */
func AppendFile(filename string, buf []byte) (err error) {
	f, err := OpenAppendFile(filename, true)
	if err != nil {
		return
	}
	defer f.Close()

	_, err = f.Write(buf)
	return
}

/**
 * @description: 递归罗列文件夹内所有内容（含子目录）
 * @param {string} pathname
 * @return {*}
 */
func GetAllFiles(pathname string) (files []string) {
	pathname = filepath.Clean(pathname)
	rd, err := ioutil.ReadDir(pathname)
	if err != nil {
		return nil
	}
	newPath := ""
	if err == nil {
		for _, fi := range rd {
			if fi.Name() == ".DS_Store" {
				continue
			}
			newPath = filepath.Join(pathname, fi.Name())
			files = append(files, newPath)
			if fi.IsDir() {
				files = append(files, GetAllFiles(newPath)...)
			}
		}
	}
	return
}

func RelativePath(path1, path2 string) (string, error) {
	if !filepath.IsAbs(path1) {
		path1, _ = filepath.Abs(path1)
	}
	if !filepath.IsAbs(path2) {
		path2, _ = filepath.Abs(path2)
	}
	return filepath.Rel(path1, path2)
}

// 文件读取到最后
func FileReadToEnd(filename string, offset int64) (data []byte, err error) {
	f, err := OpenRDonlyFile(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	f.Seek(offset, io.SeekStart)

	buffer := bytes.Buffer{}
	buf := make([]byte, BufSize)

	for {
		n, err := f.Read(buf)
		if err == io.EOF {
			break
		}
		buffer.Write(buf[:n])
	}

	return buffer.Bytes(), nil
}

func FileCropBlock(filename string, offset, size int64) error {
	fi, err := os.Stat(filename)
	if err != nil {
		return err
	}
	if fi.Size() < offset+size {
		return errors.New("beyond file size")
	} else if fi.Size() == offset+size {
		return os.Truncate(filename, offset)
	} else {
		lbuf, err := FileReadToEnd(filename, offset+size)
		if err != nil {
			return err
		}
		os.Truncate(filename, offset)
		return AppendFile(filename, lbuf)
	}
}

// 替换文件中的某一块
func FileReplaceBlock(filename string, offset, size int64, newBytes []byte) error {
	f, err := OpenWRonlyFile(filename, false)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.WriteAt(newBytes[:size], offset)
	return err
}

func OpenAppendFile(filename string, autoCreate bool) (f *os.File, err error) {
	if autoCreate {
		return os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
	} else {
		return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.ModePerm)
	}
}
func OpenRDonlyFile(filename string) (f *os.File, err error) {
	return os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
}
func OpenWRonlyFile(filename string, autoCreate bool) (f *os.File, err error) {
	if autoCreate {
		return os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	} else {
		return os.OpenFile(filename, os.O_WRONLY, os.ModePerm)
	}
}

// 清除文件的一块
func ClearBlock(filename string, offset, size int64) (nn int64, err error) {
	fi, err := os.Stat(filename)
	if err != nil {
		return
	}

	fstart, err := os.OpenFile(filename, os.O_WRONLY, os.ModePerm)
	if err != nil {
		return
	}
	fstart.Seek(offset, io.SeekStart)
	wt := bufio.NewWriter(fstart)
	defer func() {
		wt.Flush()
		fstart.Close()
	}()

	fend, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
	if err != nil {
		return
	}
	rd := bufio.NewReader(fend)

	defer fend.Close()

	_, err = fend.Seek(offset, 0)
	if err != nil {
		return
	}

	nn, err = io.Copy(wt, rd)

	os.Truncate(filename, fi.Size()-size)
	return
}

var fileTypeMap sync.Map

func init() {
	fileTypeMap.Store("ffd8ffe000104a464946", "jpg")  //JPEG (jpg)
	fileTypeMap.Store("89504e470d0a1a0a0000", "png")  //PNG (png)
	fileTypeMap.Store("47494638396126026f01", "gif")  //GIF (gif)
	fileTypeMap.Store("49492a00227105008037", "tif")  //TIFF (tif)
	fileTypeMap.Store("424d228c010000000000", "bmp")  //16色位图(bmp)
	fileTypeMap.Store("424d8240090000000000", "bmp")  //24位位图(bmp)
	fileTypeMap.Store("424d8e1b030000000000", "bmp")  //256色位图(bmp)
	fileTypeMap.Store("41433130313500000000", "dwg")  //CAD (dwg)
	fileTypeMap.Store("3c21444f435459504520", "html") //HTML (html)   3c68746d6c3e0  3c68746d6c3e0
	fileTypeMap.Store("3c68746d6c3e0", "html")        //HTML (html)   3c68746d6c3e0  3c68746d6c3e0
	fileTypeMap.Store("3c21646f637479706520", "htm")  //HTM (htm)
	fileTypeMap.Store("48544d4c207b0d0a0942", "css")  //css
	fileTypeMap.Store("696b2e71623d696b2e71", "js")   //js
	fileTypeMap.Store("7b5c727466315c616e73", "rtf")  //Rich Text Format (rtf)
	fileTypeMap.Store("38425053000100000000", "psd")  //Photoshop (psd)
	fileTypeMap.Store("46726f6d3a203d3f6762", "eml")  //Email [Outlook Express 6] (eml)
	fileTypeMap.Store("d0cf11e0a1b11ae10000", "doc")  //MS Excel 注意：word、msi 和 excel的文件头一样
	fileTypeMap.Store("d0cf11e0a1b11ae10000", "vsd")  //Visio 绘图
	fileTypeMap.Store("5374616E64617264204A", "mdb")  //MS Access (mdb)
	fileTypeMap.Store("252150532D41646F6265", "ps")
	fileTypeMap.Store("255044462d312e350d0a", "pdf")  //Adobe Acrobat (pdf)
	fileTypeMap.Store("2e524d46000000120001", "rmvb") //rmvb/rm相同
	fileTypeMap.Store("464c5601050000000900", "flv")  //flv与f4v相同
	fileTypeMap.Store("00000020667479706d70", "mp4")
	fileTypeMap.Store("49443303000000002176", "mp3")
	fileTypeMap.Store("000001ba210001000180", "mpg") //
	fileTypeMap.Store("3026b2758e66cf11a6d9", "wmv") //wmv与asf相同
	fileTypeMap.Store("52494646e27807005741", "wav") //Wave (wav)
	fileTypeMap.Store("52494646d07d60074156", "avi")
	fileTypeMap.Store("4d546864000000060001", "mid") //MIDI (mid)
	fileTypeMap.Store("504b0304140000000800", "zip")
	fileTypeMap.Store("526172211a0700cf9073", "rar")
	fileTypeMap.Store("235468697320636f6e66", "ini")
	fileTypeMap.Store("504b03040a0000000000", "jar")
	fileTypeMap.Store("4d5a9000030000000400", "exe")        //可执行文件
	fileTypeMap.Store("3c25402070616765206c", "jsp")        //jsp文件
	fileTypeMap.Store("4d616e69666573742d56", "mf")         //MF文件
	fileTypeMap.Store("3c3f786d6c2076657273", "xml")        //xml文件
	fileTypeMap.Store("494e5345525420494e54", "sql")        //xml文件
	fileTypeMap.Store("7061636b616765207765", "java")       //java文件
	fileTypeMap.Store("406563686f206f66660d", "bat")        //bat文件
	fileTypeMap.Store("1f8b0800000000000000", "gz")         //gz文件
	fileTypeMap.Store("6c6f67346a2e726f6f74", "properties") //bat文件
	fileTypeMap.Store("cafebabe0000002e0041", "class")      //bat文件
	fileTypeMap.Store("49545346030000006000", "chm")        //bat文件
	fileTypeMap.Store("04000000010000001300", "mxp")        //bat文件
	fileTypeMap.Store("504b0304140006000800", "docx")       //docx文件
	fileTypeMap.Store("d0cf11e0a1b11ae10000", "wps")        //WPS文字wps、表格et、演示dps都是一样的
	fileTypeMap.Store("6431303a637265617465", "torrent")
	fileTypeMap.Store("6D6F6F76", "mov")         //Quicktime (mov)
	fileTypeMap.Store("FF575043", "wpd")         //WordPerfect (wpd)
	fileTypeMap.Store("CFAD12FEC5FD746F", "dbx") //Outlook Express (dbx)
	fileTypeMap.Store("2142444E", "pst")         //Outlook (pst)
	fileTypeMap.Store("AC9EBD8F", "qdf")         //Quicken (qdf)
	fileTypeMap.Store("E3828596", "pwl")         //Windows Password (pwl)
	fileTypeMap.Store("2E7261FD", "ram")         //Real Audio (ram)
}

// 获取前面结果字节的二进制
func bytesToHexString(src []byte) string {
	res := bytes.Buffer{}
	if src == nil || len(src) <= 0 {
		return ""
	}
	temp := make([]byte, 0)
	for _, v := range src {
		sub := v & 0xFF
		hv := hex.EncodeToString(append(temp, sub))
		if len(hv) < 2 {
			res.WriteString(strconv.FormatInt(int64(0), 10))
		}
		res.WriteString(hv)
	}
	return res.String()
}

// 用文件前面几个字节来判断
// fSrc: 文件字节流（就用前面几个字节）
func GetFileType(fSrc []byte) string {
	var fileType string
	fileCode := bytesToHexString(fSrc)

	fileTypeMap.Range(func(key, value interface{}) bool {
		k := key.(string)
		v := value.(string)
		if strings.HasPrefix(fileCode, strings.ToLower(k)) ||
			strings.HasPrefix(k, strings.ToLower(fileCode)) {
			fileType = v
			return false
		}
		return true
	})
	return fileType
}

// 文件 ContentType
func GetFileContentType(out *os.File) (string, error) {

	// 只需要前 512 个字节就可以了
	buffer := make([]byte, 512)

	_, err := out.Read(buffer)
	if err != nil {
		return "", err
	}

	contentType := http.DetectContentType(buffer)

	return contentType, nil
}
